/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.src.nodes;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
import java.beans.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ResourceBundle;
import org.openide.src.*;
import org.openide.nodes.*;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListener;
import org.openide.util.actions.SystemAction;
import org.openide.util.datatransfer.ExTransferable;
/** Superclass of nodes representing elements in the source hierarchy.
* <p>Element nodes generally:
* <ul>
* <li>Have an associated icon, according to {@link #resolveIconBase}.
* <li>Have a display name based on the element's properties, using {@link #elementFormat};
* changes to {@link ElementFormat#dependsOnProperty relevant} element properties
* automatically affect the display name.
* <li>Have some node properties (displayable on the property sheet), according to
* the element's properties, and with suitable editors.
* <li>Permit renames and deletes, if a member element and writeable.
* <li>As permitted by the element, and a writable flag in the node,
* permit cut/copy/paste operations, as well as creation of new members.
* </ul>
*
* @author Petr Hamernik
*/
public abstract class ElementNode extends AbstractNode implements IconStrings, ElementProperties {
/** Source of the localized human presentable strings. */
static ResourceBundle bundle = NbBundle.getBundle(ElementNode.class);
/** Options for the display name format. */
protected static final SourceOptions sourceOptions = (SourceOptions)SourceOptions.findObject(SourceOptions.class, true);
/** Default return value of getIconAffectingProperties method. */
private static final String[] ICON_AFFECTING_PROPERTIES = new String[] {
PROP_MODIFIERS
};
/** Associated element. */
protected Element element;
/** Format for {@link java.beans.FeatureDescriptor#getDisplayName}. */
protected ElementFormat elementFormat = new ElementFormat (""); // NOI18N
/** Is this node read-only or are modifications permitted? */
protected boolean writeable;
/** Listener to forbid its garbage collection */
private transient PropertyChangeListener listener;
/** Create a new element node.
*
* @param element element to represent
* @param children child nodes
* @param writeable <code>true</code> if this node should allow modifications.
* These include writable properties, clipboard operations, deletions, etc.
*/
public ElementNode(Element element, Children children, boolean writeable) {
super(children);
this.element = element;
this.writeable = writeable;
setIconBase(resolveIconBase());
setDisplayName(getElementFormat().format(element));
listener = new ElementListener();
element.addPropertyChangeListener(WeakListener.propertyChange (listener, element));
displayFormat = null;
}
/* Gets the short description of this node.
* @return A localized short description associated with this node.
*/
public String getShortDescription() {
try {
return getHintElementFormat().format(element);
}
catch (IllegalArgumentException e) {
return super.getShortDescription();
}
}
/** Get the currently appropriate icon base.
* Subclasses should make this sensitive to the state of the element--for example,
* a private variable may have a different icon than a public one.
* The icon will be automatically changed whenever a
* {@link #getIconAffectingProperties relevant} change is made to the element.
* @return icon base
* @see AbstractNode#setIconBase
*/
abstract protected String resolveIconBase();
/** Get the names of all element properties which might affect the choice of icon.
* The default implementation just returns {@link #PROP_MODIFIERS}.
* @return the property names, from {@link ElementProperties}
*/
protected String[] getIconAffectingProperties() {
return ICON_AFFECTING_PROPERTIES;
}
/** Get a format for the element's display name.
* The display name will be automatically updated whenever a
* {@link ElementFormat#dependsOnProperty relevant}
* change is made to the element.
* @return the format
*/
public final ElementFormat getElementFormat() {
return elementFormat;
}
/** Set the format for the display name.
* @param elementFormat the new format
*/
public final void setElementFormat(ElementFormat elementFormat) {
this.elementFormat = elementFormat;
setDisplayName(elementFormat.format(ElementNode.this.element));
}
/** Get a format for creating this node's
* {@link java.beans.FeatureDescriptor#getShortDescription short description}.
*/
abstract protected ElementFormat getHintElementFormat();
public HelpCtx getHelpCtx () {
return new HelpCtx (ElementNode.class);
}
/** Test whether this node can be renamed.
* The default implementation assumes it can if this node is {@link #writeable}.
*
* @return <code>true</code> if this node can be renamed
*/
public boolean canRename() {
return writeable;
}
/** Test whether this node can be deleted.
* The default implementation assumes it can if this node is {@link #writeable}.
*
* @return <code>true</code> if this node can be renamed
*/
public boolean canDestroy () {
return writeable;
}
/* Copy this node to the clipboard.
*
* @return {@link ExTransferable.Single} with one flavor, {@link NodeTransfer#nodeCopyFlavor}
* @throws IOException if it could not copy
*/
public Transferable clipboardCopy () throws IOException {
ExTransferable ex = ExTransferable.create(super.clipboardCopy());
ex.put(new ElementStringTransferable());
return ex;
}
/* Cut this node to the clipboard.
*
* @return {@link ExTransferable.Single} with one flavor, {@link NodeTransfer#nodeCopyFlavor}
* @throws IOException if it could not cut
*/
public Transferable clipboardCut () throws IOException {
if (!writeable)
throw new IOException();
ExTransferable ex = ExTransferable.create(super.clipboardCut());
ex.put(new ElementStringTransferable());
return ex;
}
/** Transferable for elements as String. */
class ElementStringTransferable extends ExTransferable.Single {
/** Construct new Transferable for this node. */
ElementStringTransferable() {
super(DataFlavor.stringFlavor);
}
/** @return the data as String */
protected Object getData() {
return element.toString();
}
}
/** Test whether this node can be copied.
* The default implementation returns <code>true</code>.
* @return <code>true</code> if it can
*/
public boolean canCopy () {
return true;
}
/** Test whether this node can be cut.
* The default implementation assumes it can if this node is {@link #writeable}.
* @return <code>true</code> if it can
*/
public boolean canCut () {
return writeable;
}
/** Set all actions for this node.
* @param actions new list of actions
*/
public void setActions(SystemAction[] actions) {
systemActions = actions;
}
/** Calls super.fireCookieChange. The reason why is redefined
* is only to allow the access from this package.
*/
void superFireCookieChange() {
fireCookieChange();
}
/** Get a cookie from this node.
* First tries the node itself, then {@link Element#getCookie}.
* Since {@link Element} implements <code>Node.Cookie</code>, it is
* possible to find the element from a node using code such as:
* <p><code><pre>
* Node someNode = ...;
* MethodElement element = (MethodElement) someNode.getCookie (MethodElement.class);
* if (element != null) { ... }
* </pre></code>
* @param type the cookie class
* @return the cookie or <code>null</code>
*/
public Node.Cookie getCookie (Class type) {
Node.Cookie c = super.getCookie(type);
if (c == null)
c = element.getCookie(type);
return c;
}
/** Test for equality.
* @return <code>true</code> if the represented {@link Element}s are equal
*/
public boolean equals (Object o) {
return (o instanceof ElementNode) && (element.equals (((ElementNode)o).element));
}
/** Get a hash code.
* @return the hash code from the represented {@link Element}
*/
public int hashCode () {
return element.hashCode ();
}
void superSetName(String name) {
super.setName(name);
}
void superPropertyChange (String name, Object o, Object n) {
super.firePropertyChange (name, o, n);
}
void superShortDescriptionChange (String o, String n) {
super.fireShortDescriptionChange(o, n);
}
// ================== Element listener =================================
/** Listener for changes of the element's property changes.
* It listens and changes updates the iconBase and displayName
* if the changed property could affect them.
*/
private class ElementListener implements PropertyChangeListener {
/** Called when any element's property changed.
*/
public void propertyChange(PropertyChangeEvent evt) {
String propName = evt.getPropertyName();
if (propName == null) {
setDisplayName(getElementFormat().format(ElementNode.this.element));
setIconBase(resolveIconBase());
}
else {
// display name
if (getElementFormat().dependsOnProperty(propName))
setDisplayName(getElementFormat().format(ElementNode.this.element));
// icon
String[] iconProps = getIconAffectingProperties();
for (int i = 0; i < iconProps.length; i++) {
if (iconProps[i].equals(propName)) {
setIconBase(resolveIconBase());
break;
}
}
if (propName.equals(ElementProperties.PROP_NAME)) {
// set inherited name - this code should rather in MemberElementNode,
// but we safe one instance of listener for each node
// if it will be here. [Petr]
try {
superSetName(((MemberElement)ElementNode.this.element).getName().toString());
}
catch (ClassCastException e) {
// it is strange - PROP_NAME has only member element.
}
}
else {
if (propName.equals(Node.PROP_COOKIE)) {
// Fires the changes of the cookies of the element.
superFireCookieChange();
return;
}
}
}
if (getHintElementFormat().dependsOnProperty(evt.getPropertyName()))
superShortDescriptionChange("", getShortDescription()); // NOI18N
superPropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
}
}
// ================== Property support for element nodes =================
/** Property support for element nodes properties.
*/
static abstract class ElementProp extends PropertySupport {
/** Constructs a new ElementProp - support for properties of
* element hierarchy nodes.
*
* @param name The name of the property
* @param type The class type of the property
* @param canW The canWrite flag of the property
*/
public ElementProp(String name, java.lang.Class type, boolean canW) {
super(name, type,
bundle.getString("PROP_" + name),
bundle.getString("HINT_" + name),
true, canW);
}
/** Setter for the value. This implementation only tests
* if the setting is possible.
*
* @param val the value of the property
* @exception IllegalAccessException when this ElementProp was constructed
* like read-only.
*/
public void setValue (Object val) throws IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
if (!canWrite())
throw new IllegalAccessException(bundle.getString("MSG_Cannot_Write"));
}
/** Invokes the runnable using NbDocument.runAtomic.
* If BadLocationException occured inside, it will be thrown
* the new SourceException.
*/
void runAtomic(Element element, final SourceEditSupport.ExceptionalRunnable exRun) throws InvocationTargetException {
final SourceException[] ex = { null };
try {
SourceElement source = SourceEditSupport.findSource(element);
Runnable run = new Runnable() {
public void run() {
try {
exRun.run();
}
catch (SourceException e) {
ex[0] = e;
}
}
};
source.runAtomicAsUser(run);
}
catch (SourceException e) {
ex[0] = e;
}
if (ex[0] != null) {
throw new InvocationTargetException(ex[0]);
}
}
}
}
/*
* Log
* 32 Gandalf-post-FCS1.30.2.0 4/17/00 Svatopluk Dedic Initializes node display
* format from SourceOptions option.
* 31 src-jtulach1.30 1/15/00 Jaroslav Tulach Compiles with JDK 1.3
* 30 src-jtulach1.29 1/12/00 Petr Hamernik i18n using perl script
* (//NOI18N comments added)
* 29 src-jtulach1.28 1/8/00 Radko Najman fixed bug #2568
* 28 src-jtulach1.27 11/5/99 Jaroslav Tulach WeakListener has now
* registration methods.
* 27 src-jtulach1.26 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 26 src-jtulach1.25 10/7/99 Petr Hamernik formating and a small
* improvement
* 25 src-jtulach1.24 9/13/99 Petr Hamernik runAsUser implemented and
* used
* 24 src-jtulach1.23 7/8/99 Jesse Glick Context help.
* 23 src-jtulach1.22 7/1/99 Petr Hamernik Clipboard operations on
* source Elements
* 22 src-jtulach1.21 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 21 src-jtulach1.20 4/26/99 Jesse Glick [JavaDoc]
* 20 src-jtulach1.19 4/26/99 Petr Hamernik getCookie minor changes
* 19 src-jtulach1.18 4/21/99 Petr Hamernik debug pring removed
* 18 src-jtulach1.17 4/20/99 Petr Hamernik weak listener bugfix
* 17 src-jtulach1.16 4/20/99 Petr Hamernik debugs removed, small
* bugfix
* 16 src-jtulach1.15 4/20/99 Jaroslav Tulach
* 15 src-jtulach1.14 4/13/99 Petr Hamernik getCookie improved
* 14 src-jtulach1.13 4/12/99 Jesse Glick [JavaDoc]
* 13 src-jtulach1.12 4/9/99 Jan Jancura Bug 1507 equals added
* 12 src-jtulach1.11 4/8/99 Jan Jancura equals added
* 11 src-jtulach1.10 4/2/99 Jesse Glick [JavaDoc]
* 10 src-jtulach1.9 4/2/99 Jesse Glick [JavaDoc]
* 9 src-jtulach1.8 4/1/99 Jan Jancura Object Browser support
* 8 src-jtulach1.7 3/26/99 Petr Hamernik fire of properties -
* bugfix
* 7 src-jtulach1.6 3/18/99 Petr Hamernik
* 6 src-jtulach1.5 3/18/99 Petr Hamernik
* 5 src-jtulach1.4 3/16/99 Petr Hamernik properties improvements
* 4 src-jtulach1.3 3/15/99 Petr Hamernik
* 3 src-jtulach1.2 3/15/99 Petr Hamernik
* 2 src-jtulach1.1 3/15/99 Petr Hamernik
* 1 src-jtulach1.0 3/12/99 Petr Hamernik
* $
*/